package pl.enigmatic.util;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.Vector;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;


public class XML {

	private XML() {}

	private static final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
	static {
		factory.setNamespaceAware(true);
	}

	/**
	 * parsuje wejściowy strumień do dokumentu xml
	 * 
	 * @param is strumień wejściowy zawierający parsowany dokument
	 * @return dokument xml lub <code>null</code> gdy wystąpi jakiś błąd
	 */
	public static Document parseStream(final InputStream is) {
		try {
			final DocumentBuilder db = factory.newDocumentBuilder();
			final Document doc = db.parse(is);
			doc.getDocumentElement().normalize();
			return doc;
		} catch (final Exception e) {}
		return null;
	}

	/**
	 * zwraca pierwsze wystąpienie elementu o znaczniku <code>tag</code> w elemencie <code>parent</code>
	 * 
	 * @param parent przeszukiwany element (rodzic)
	 * @param tag znacznik poszukiwanego dziecka
	 * @return pierwszy element <code>tag</code> w elemencie <code>parent</code> lub <code>null</code>, gdy takiego nie ma
	 */
	public static Element getNode(final Element parent, final String tag) {
		final NodeList nodes = parent.getChildNodes();
		for (int i = 0; i < nodes.getLength(); i++) {
			final Node node = nodes.item(i);
			if (node.getNodeType() == Node.ELEMENT_NODE && (tag == null || ((Element) node).getTagName().equals(tag))) { return (Element) node; }
		}
		return null;
	}

	/**
	 * zwraca wszystkie wystąpienia elementu o znaczniku <code>tag</code> w elemencie <code>parent</code>
	 * 
	 * @param parent przeszukiwany element (rodzic)
	 * @param tag znacznik poszukiwanego dziecka
	 * @return wektor dzieci odpowiadających kryterium
	 */
	public static Vector<Element> getNodes(final Element parent, final String tag) {
		final Vector<Element> res = new Vector<Element>();
		final NodeList nodes = parent.getChildNodes();
		for (int i = 0; i < nodes.getLength(); i++) {
			final Node node = nodes.item(i);
			if (node.getNodeType() == Node.ELEMENT_NODE && (tag == null || ((Element) node).getTagName().equals(tag))) {
				res.add((Element) node);
			}
		}
		return res;
	}

	public static Vector<Element> getNodes(final Document doc, final String tag) {
		return getNodes(doc.getDocumentElement(), tag);
	}

	public static Element getNode(final Document doc, final String tag) {
		return getNode(doc.getDocumentElement(), tag);
	}

	public static Vector<Element> getNodes(final InputStream is, final String tag) {
		return getNodes(parseStream(is), tag);
	}

	public static Vector<Element> getNodes(final InputStream is) {
		return getNodes(is, null);
	}

	public static Element getNode(final Element src, final String... path) {
		if (path.length > 0) {
			Element res = src;
			for (final String element : path) {
				final Vector<Element> c = getNodes(res, element);
				if (c.isEmpty()) {
					return null;
				} else {
					res = c.get(0);
				}
			}
			return res;
		}
		return null;
	}

	public static Element getNode(final Document src, final String... path) {
		return getNode(src.getDocumentElement(), path);
	}

	public static Element getNode(final InputStream is, final String... path) {
		return getNode(parseStream(is), path);
	}

	public static Document parseString(final String string) {
		try {
			return parseStream(new ByteArrayInputStream(string.getBytes("UTF-8")));
		} catch (final Exception e) {
			return null;
		}
	}

	public static String getText(final Node node) {
		String res = null;
		if (node.hasChildNodes()) {
			res = node.getFirstChild().getTextContent();
		}
		if (Strings.empty(res)) {
			res = node.getNodeValue();
		}
		return res != null ? Strings.decode(Strings.trimAllWhitespaces(res)) : res;
	}

	public static Node insertAfter(Node parent, Node newChild, Node refChild) {
		Node next = refChild.getNextSibling();
		if (next == null) {
			return parent.appendChild(newChild);
		} else {
			return parent.insertBefore(newChild, next);
		}
	}

	public static void changeName(Element node, String namespace, String name) {
		Document doc = node.getOwnerDocument();
		// Create an element with the new name
		Element changed = doc.createElementNS(namespace, name);

		// Copy the attributes to the new element
		NamedNodeMap attrs = node.getAttributes();
		for (int i = 0; i < attrs.getLength(); i++) {
			Attr attr = (Attr) doc.importNode(attrs.item(i), true);
			changed.getAttributes().setNamedItem(attr);
		}

		// Move all the children
		while (node.hasChildNodes()) {
			changed.appendChild(node.getFirstChild());
		}

		// Replace the old node with the new node
		node.getParentNode().replaceChild(changed, node);
	}
}
